package signature

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"os"

	"gitee.com/terender/sfw/utils"
)

// decodeKeyFromPemFile 从pem格式文件中解码出秘钥
func decodeKeyFromPemFile(filename string) ([]byte, error) {
	if _, err := os.Stat(filename); err != nil {
		return nil, err
	}
	file, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	block, _ := pem.Decode(file)
	if block == nil {
		return nil, fmt.Errorf(`Read key from %v failed`, filename)
	}
	return block.Bytes, nil
}

// ReadPrivKey 读取文件中的私钥
func ReadPrivKey(keyName string) (*rsa.PrivateKey, error) {
	privKeyFile := utils.ProjectPath() + `/static/keystore/` + keyName + `.private.key`
	key, err := decodeKeyFromPemFile(privKeyFile)
	if err != nil {
		return nil, err
	}

	privateKey, err := x509.ParsePKCS1PrivateKey(key)
	if err != nil {
		return nil, err
	}
	return privateKey, nil
}

// ReadPubKey 读取文件中的公钥
func ReadPubKey(keyName string) (*rsa.PublicKey, error) {
	pubKeyFile := utils.ProjectPath() + `/static/keystore/` + keyName + `.public.key`
	key, err := decodeKeyFromPemFile(pubKeyFile)
	if err != nil {
		return nil, err
	}
	p, err := x509.ParsePKIXPublicKey(key)
	if err != nil {
		return nil, err
	}
	publickKey, ok := p.(*rsa.PublicKey)
	if !ok {
		return nil, fmt.Errorf(`Read public key from %v failed: not valid RSA public key`, pubKeyFile)
	}
	return publickKey, nil
}

// Sign 使用指定私钥对数据进行签名
// 用 SHA-256 算法对数据进行摘要
func Sign(data []byte, priv *rsa.PrivateKey) ([]byte, error) {
	hashed := sha256.Sum256(data)
	sig, err := rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA256, hashed[:])
	if err != nil {
		return nil, err
	}
	return sig, nil
}

// Verify 使用公钥对数据进行验签
// 用 SHA-256 算法对数据进行摘要
func Verify(data, sig []byte, pub *rsa.PublicKey) (bool, error) {
	hashed := sha256.Sum256(data)
	err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, hashed[:], sig)
	if err != nil {
		return false, err
	}
	return true, nil
}

// GenerateKeyPairs 生成指定名字的指定bit位数公私钥对
func GenerateKeyPairs(keyName string, bits int) (privKey *rsa.PrivateKey, err error) {
	err = utils.CreatePath(utils.ProjectPath() + `/static/keystore/`)
	if err != nil {
		return nil, err
	}

	privKeyFile := utils.ProjectPath() + `/static/keystore/` + keyName + `.private.key`
	pubKeyFile := utils.ProjectPath() + `/static/keystore/` + keyName + `.public.key`

	priv, err := rsa.GenerateKey(rand.Reader, bits)
	if err != nil {
		return nil, err
	}

	block := &pem.Block{
		Type:  `RSA PRIVATE KEY`,
		Bytes: x509.MarshalPKCS1PrivateKey(priv),
	}
	bytes := pem.EncodeToMemory(block)
	err = ioutil.WriteFile(privKeyFile, bytes, 0666)
	if err != nil {
		return nil, err
	}

	pub, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
	if err != nil {
		return nil, err
	}
	block = &pem.Block{
		Type:  `RSA PUBLIC KEY`,
		Bytes: pub,
	}
	bytes = pem.EncodeToMemory(block)
	err = ioutil.WriteFile(pubKeyFile, bytes, 0666)
	if err != nil {
		return nil, err
	}

	return priv, nil
}
